package org.jdemo.orm.mybatis.support;

import org.apache.commons.beanutils.PropertyUtils;
import org.jdemo.orm.mybatis.exception.BizCheckedException;
import org.jdemo.orm.mybatis.support.annotation.Table;
import org.jdemo.orm.mybatis.support.mybatis.MybatisContext;
import org.jdemo.orm.mybatis.support.mybatis.MybatisLoader;
import org.jdemo.orm.mybatis.support.mybatis.MybatisStatement;
import org.jdemo.orm.mybatis.support.spring.SpringContextHolder;
import org.jdemo.util.CollectionUtil;
import org.mybatis.spring.SqlSessionTemplate;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 通用DAO
 *
 * @author longlin(longlin@cyou-inc.com)
 * @date 202014/4/14
 * @since V1.0
 */
public class BaseDAO<T, P> {
    protected SqlSessionTemplate sqlSessionTemplate;

    /**
     * 字段属性映射表
     */
    protected Map<String, String> columnPropertyMap;
    /**
     * 实体名
     */
    protected String entityName;
    /**
     * 实体类型
     */
    protected Class<T> entityClass;
    /**
     * 实体全部属性
     */
    protected String[] entityProperties;
    /**
     * 实体主键
     */
    protected String idProperty;
    /**
     * 表名
     */
    protected String tableName;
    /**
     * 表全部字段
     */
    protected String[] tableColumns;
    /**
     * 主键是否自增长
     */
    protected boolean autoIncrement;
    /**
     * 表主键
     */
    protected String idColumn;

    /**
     * 默认的mybatis查询语句
     */
    protected MybatisStatement statement;

    /**
     * 构造器
     *
     * @param entityClass       实体类型
     * @param columnPropertyMap 字段属性映射表
     */
    protected BaseDAO(Class<T> entityClass, Map<String, String> columnPropertyMap) {
        this.entityClass = entityClass;
        this.entityName = entityClass.getSimpleName();

        this.columnPropertyMap = columnPropertyMap;
        this.tableColumns = CollectionUtil.toArray(columnPropertyMap.keySet());
        this.entityProperties = CollectionUtil.toArray(columnPropertyMap.values());

        Table table = entityClass.getAnnotation(Table.class);
        this.tableName = table.table();
        this.autoIncrement = table.autoIncrement();
        this.idColumn = table.idColumn();
        this.idProperty = columnPropertyMap.get(this.idColumn);

        this.sqlSessionTemplate = SpringContextHolder.getBean(SqlSessionTemplate.class);

        //构建通用查询语句
        this.statement = new MybatisStatement(this.entityName);

        //载入mybatis配置文件
        String mapperResource = buildMapperResourcePath();
        MybatisLoader<T> mybatisLoader = new MybatisLoader<T>(this.sqlSessionTemplate.getConfiguration(),
                entityClass, entityName, autoIncrement, columnPropertyMap, idProperty, idColumn);
        mybatisLoader.loadAlias();
        mybatisLoader.loadResultMap();
        mybatisLoader.loadExtendFragment(mapperResource);
        mybatisLoader.loadEntityMapper(mapperResource);
        mybatisLoader.loadExtendStatement(mapperResource);
    }

    /**
     * 获取Mybatis的mapper路径
     *
     * @return
     */
    private String buildMapperResourcePath() {
        return this.entityClass.getName().replace(".entity.", ".mapper.").replaceAll("\\.", "/") + "Mapper.xml";
    }

    /**
     * 获取实体对象的属性值
     *
     * @param entity   实体对象
     * @param property 属性名
     * @param <E>      属性类型
     * @return 返回属性值
     */
    @SuppressWarnings("unchecked")
    private <E> E getEntityPropertyValue(T entity, String property) {
        try {
            return (E) PropertyUtils.getProperty(entity, property);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据id判断对象是否存在
     *
     * @param id 主键编号
     * @return 返回对象
     */
    public boolean exist(P id) {
        return get(id, idColumn) != null;
    }

    /**
     * 根据查询参数判断对象是否存在
     *
     * @param param 查询参数
     * @return 返回对象
     */
    public boolean exist(Parameter param) {
        return get(param, idColumn) != null;
    }

    /**
     * 根据id获取对象
     *
     * @param id 主键编号
     * @return 返回对象
     */
    public T get(P id) {
        return get(id, this.tableColumns);
    }

    /**
     * 根据id获取指定字段的对象
     *
     * @param id           主键编号
     * @param tableColumns 指定表的字段
     * @return 返回对象
     */
    public T get(P id, String... tableColumns) {
        if (id == null) {
            throw new BizCheckedException("id can't be null");
        }
        return get(Parameter.query(this.idProperty, id), tableColumns);
    }

    /**
     * 根据查询参数获取对象
     *
     * @param param 查询参数
     * @return 返回对象
     */
    public T get(Parameter param) {
        return get(param, this.tableColumns);
    }

    /**
     * 根据查询参数获取对象
     *
     * @param param        查询参数
     * @param tableColumns 指定表的字段
     * @return 返回对象
     */
    public T get(Parameter param, String... tableColumns) {
        List<T> list = findList(param, tableColumns);
        return (list != null && list.size() > 0) ? list.get(0) : null;
    }

    /**
     * 根据ids查询对象列表
     *
     * @param ids 主键编号
     * @return 返回对象列表
     */
    public List<T> findByIds(Collection<P> ids) {
        return findByIds(ids, this.tableColumns);
    }

    /**
     * 根据ids查询对象列表
     *
     * @param ids          主键编号
     * @param tableColumns 指定表的字段
     * @return 返回对象列表
     */
    public List<T> findByIds(Collection<P> ids, String... tableColumns) {
        if (ids == null || ids.size() == 0) {
            throw new BizCheckedException("ids can't be empty");
        }
        return findList(Parameter.query(MybatisContext.QUERY_IDS, ids), tableColumns);
    }

    /**
     * 根据参数查询前N条列表
     *
     * @param param 查询参数
     * @param top   返回数量
     * @return 返回对象列表
     */
    public List<T> findTop(Parameter param, int top) {
        return findTop(param, top, this.tableColumns);
    }

    /**
     * 根据参数查询前N条列表
     *
     * @param param        查询参数
     * @param top          返回数量
     * @param tableColumns 指定表的字段
     * @return 返回对象列表
     */
    public List<T> findTop(Parameter param, int top, String... tableColumns) {
        return findList(param.start(0).limit(top));
    }

    /**
     * 根据参数分页查询列表
     *
     * @param param    查询参数
     * @param pageNo   当前页
     * @param pageSize 每页记录数
     * @return 返回对象列表
     */
    public Paged<T> findPage(Parameter param, int pageNo, int pageSize) {
        return findPage(param, pageNo, pageSize, this.tableColumns);
    }

    /**
     * 根据参数分页查询列表
     *
     * @param param        查询参数
     * @param pageNo       当前页
     * @param pageSize     每页记录数
     * @param tableColumns 指定表的字段
     * @return 返回对象列表
     */
    public Paged<T> findPage(Parameter param, int pageNo, int pageSize, String... tableColumns) {
        param.start(((pageNo - 1) * pageSize)).limit(pageSize);
        return new Paged<T>(count(param), pageNo, pageSize, findList(param, tableColumns));
    }

    /**
     * 根据参数查询列表
     *
     * @param param 查询参数
     * @return 返回对象列表
     */
    public List<T> findList(Parameter param) {
        return findList(param, this.tableColumns);
    }

    /**
     * 根据参数查询列表
     *
     * @param param        查询参数
     * @param tableColumns 指定表的字段
     * @return 返回对象列表
     */
    public List<T> findList(Parameter param, String... tableColumns) {
        return sqlSessionTemplate.selectList(statement.getListStatement(), param.toMap(this.tableName, CollectionUtil.asList(tableColumns)));
    }

    /**
     * 查询记录数
     *
     * @param param 查询参数
     * @return 返回数量
     */
    public Integer count(Parameter param) {
        Long count = fx(param, MybatisContext.FX_COUNT, idColumn);
        return Integer.parseInt(count.toString());
    }

    /**
     * 对指定字段求和
     *
     * @param param     查询参数
     * @param sumColumn 求和字段
     * @return 返回求和值
     */
    public BigDecimal sum(Parameter param, String sumColumn) {
        return fx(param, MybatisContext.FX_SUM, sumColumn);
    }

    /**
     * 获取指定字段的最大值
     *
     * @param param     查询参数
     * @param maxColumn 求最大值字段
     * @return 返回最大值
     */
    public Object max(Parameter param, String maxColumn) {
        return fx(param, MybatisContext.FX_MAX, maxColumn);
    }

    /**
     * 获取指定字段的最小值
     *
     * @param param     查询参数
     * @param minColumn 求最小值字段
     * @return 返回最小值
     */
    public Object min(Parameter param, String minColumn) {
        return fx(param, MybatisContext.FX_MIN, minColumn);
    }

    /**
     * 执行函数查询
     *
     * @param param    查询参数
     * @param fxName   函数名
     * @param fxColumn 函数字段
     * @param <E>      返回类型
     * @return 返回函数查询结果
     */
    public <E> E fx(Parameter param, String fxName, String fxColumn) {
        return sqlSessionTemplate.selectOne(statement.getFxStatement(), param.toMap(this.tableName, fxName, fxColumn));
    }

    /**
     * 创建表记录
     * 如果主键是autoIncrement，则执行完成后entity的主键会被填充自增长值
     * 如果主键不是autoIncrement，则id不能为空
     *
     * @param entity 实体对象
     */
    public void create(T entity) {
        if (entity == null) {
            throw new BizCheckedException("entity can't be null");
        }
        if (!this.autoIncrement && getEntityPropertyValue(entity, idProperty) == null) {
            throw new BizCheckedException("entity'id can't be null");
        }
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(MybatisContext.TABLE_NAME, this.tableName);
        map.put(MybatisContext.ENTITY, entity);
        sqlSessionTemplate.insert(statement.getInsertStatement(), map);
    }

    /**
     * 批量创建表记录，不会填充entity的主键
     * 如果主键不是autoIncrement，则所有的entities的id不能为空
     *
     * @param entities 实体对象数组
     */
    public void createBatch(T... entities) {
        if (entities == null || entities.length == 0) {
            throw new BizCheckedException("entities can't be empty");
        }
        for (T entity : entities) {
            if (!this.autoIncrement && getEntityPropertyValue(entity, idProperty) == null) {
                throw new BizCheckedException("entity'id can't be null");
            }
        }
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(MybatisContext.TABLE_NAME, this.tableName);
        map.put(MybatisContext.ENTITIES, entities);
        sqlSessionTemplate.insert(statement.getInsertBatchStatement(), map);
    }

    /**
     * 更新表记录
     *
     * @param entity 实体对象
     */
    public void update(T entity) {
        update(entity, false);
    }

    /**
     * 更新表记录
     *
     * @param entity        实体对象
     * @param withNullField 是否更新值为null的字段
     */
    public void update(T entity, boolean withNullField) {
        update(entity, CollectionUtil.asList(this.entityProperties), withNullField);
    }

    /**
     * 更新表记录
     *
     * @param entity            实体对象
     * @param includeProperties 更新指定的字段
     */
    public void update(T entity, List<String> includeProperties) {
        update(entity, includeProperties, false);
    }

    /**
     * 更新表记录
     *
     * @param entity            实体对象
     * @param includeProperties 更新指定的字段
     * @param withNullField     是否更新指定字段中值为null的字段
     */
    public void update(T entity, List<String> includeProperties, boolean withNullField) {
        P idPropertyVal = getEntityPropertyValue(entity, idProperty);
        Updater updater = Updater.fromEntity(entity, includeProperties, withNullField, idProperty, entityProperties);
        update(idPropertyVal, updater);
    }

    /**
     * 通过主键更新指定字段
     *
     * @param id      主键编号
     * @param updater 字段更新参数
     */
    public void update(P id, Updater updater) {
        if (id == null) {
            throw new BizCheckedException("id can't be null");
        }
        update(Parameter.query(idProperty, id), updater);
    }

    /**
     * 通过查询参数更新指定字段
     *
     * @param param   查询参数
     * @param updater 字段更新参数
     */
    public void update(Parameter param, Updater updater) {
        if (updater.getFields().size() == 0) {
            throw new BizCheckedException("updater'fields can't be empty");
        }
        sqlSessionTemplate.update(statement.getUpdateStatement(), updater.toMap(this.tableName, param));
    }

    /**
     * 根据主键删除表记录
     *
     * @param ids 主键编号
     */
    public void delete(P... ids) {
        if (ids == null || ids.length == 0) {
            throw new BizCheckedException("ids can't be empty");
        }
        delete(Parameter.query(MybatisContext.QUERY_IDS, ids));
    }

    /**
     * 根据查询参数删除表记录
     *
     * @param param 查询参数
     */
    public void delete(Parameter param) {
        sqlSessionTemplate.delete(statement.getDeleteStatement(), param.toMap(this.tableName));
    }
}
